﻿using MongoDB.Bson;
using CRL.Data;
using CRL.Data.LambdaQuery;
using CRL.Data.LambdaQuery.CRLExpression;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;

namespace CRL.Mongo.MongoDBEx
{
    public sealed partial class MongoDBExt
    {
        //from https://www.mongodb.com/zh-cn/docs/manual/reference/sql-aggregation-comparison/

        string checkPrefix(string fieldQuery, bool flag = false)
        {
            if (fieldQuery.Contains("t1.") || flag)
            {
                return fieldQuery.Replace("t1.", "");

            }
            return fieldQuery;
        }
        void createLookup<T>(List<BsonDocument> pipeline, MongoDBLambdaQuery<T> query)
        {
            if (query.__Relations != null)
            {
                foreach (var relation in query.__Relations)
                {
                    var typeJoin = relation.Key.OriginType;
                    var joinInfo = relation.Value;
                    var asName = query.GetPrefix(typeJoin, "").TrimEnd('.');
                    var tableName = getTableName(typeJoin);
                    var bs = new BsonDocument().Add("from", tableName).Add("as", asName);
                    //like t1.name=t2.name
                    var exp = joinInfo.expression;
                    if (exp.Type == CRLExpressionType.Binary && exp.Right.Type != CRLExpressionType.Binary)
                    {
                        var field1 = joinInfo.expression.Left.Data;
                        var field2 = joinInfo.expression.Right.Data;
                        bs.Add("localField", checkPrefix(field1.ToString(), true));
                        bs.Add("foreignField", checkPrefix(field2.ToString(), true));
                    }
                    else
                    {
                        //pipeline
                        var pat = asName + @"\.(\w+)";
                        var match = Regex.Matches(exp.SqlOut, pat);
                        var bsLet = new BsonDocument();
                        foreach (Match m in match)
                        {
                            var name = m.Groups[1].Value;
                            if (!bsLet.Contains($"{asName}_{name}"))
                            {
                                bsLet.Add($"{asName}_{name}", $"${name}");
                            }
                        }
                        bs.Add("let", bsLet);
                        var expr = query.RouteCRLExpression(exp, true);
                        bs.Add("pipeline", new BsonArray().Add(new BsonDocument("$match", new BsonDocument("$expr", expr.Filter))));
                        //bs.Add("pipeline", new BsonArray().Add(new BsonDocument("$match", expr.Filter)));
                    }

                    pipeline.Add(new BsonDocument("$lookup", bs));

                    var fullJoin = joinInfo.joinType == JoinType.Inner;
                    var bs2 = new BsonDocument().Add("path", $"${asName}");
                    if (!fullJoin)
                    {
                        bs2.Add("preserveNullAndEmptyArrays", true);
                    }
                    pipeline.Add(new BsonDocument("$unwind", bs2));
                }
            }

        }
        BsonDocument getBinaryValue<T>(MongoDBLambdaQuery<T> query, CRLExpression left, CRLExpression right, ExpressionType expressionType)
        {
            var opFunc = query.getBsonOp(expressionType);
            var bsArry = new BsonArray();
            var l = left.Data;
            var r = right.Data;
            if (left.Type == CRLExpressionType.Tree)
            {
                l = getBinaryValue(query, left.Left, left.Right, left.ExpType);
            }
            if (right.Type == CRLExpressionType.Tree)
            {
                r = getBinaryValue(query, right.Left, right.Right, right.ExpType);
            }
            BsonValue getValue(CRLExpressionType type, object v)
            {
                if (type == CRLExpressionType.Value)
                {
                    return BsonValue.Create(v);
                }
                else if (type == CRLExpressionType.Name)
                {
                    return $"${v}";
                }
                return v as BsonDocument;
            }
            l = getValue(left.Type, l);
            r = getValue(right.Type, r);
            bsArry.Add(l as BsonValue);
            bsArry.Add(r as BsonValue);
            return new BsonDocument($"${opFunc}", bsArry);
        }
        BsonDocument createProject<T>(List<BsonDocument> pipeline, MongoDBLambdaQuery<T> query, ref BsonDocument groupInfo)
        {
            if (query.__SetGroupCount)
            {
                pipeline.Add(new BsonDocument("$group", new BsonDocument("_id", BsonNull.Value).Add("count", new BsonDocument("$sum", 1))));
                return null;
            }
            var selectField = query.GetFieldMapping();
            var projectDic = new BsonDocument();
            foreach (var f in selectField)
            {
                var method = f.MethodName.ToLower();
                if (!string.IsNullOrEmpty(method))
                {
                    var methodCallObj = f.CRLExpression as MethodCallObj;
                    var me = methodCallObj.BinaryExp;
                    if (me != null)
                    {
                        var bs1 = getBinaryValue(query, me.Left, me.Right, me.ExpType);
                        groupInfo?.Add(f.ResultName, new BsonDocument("$" + method.ToLower(), bs1));
                    }
                    else
                    {
                        BsonValue args;
                        if (method == "count")
                        {
                            method = "sum";
                            args = 1;
                        }
                        else
                        {
                            var memberName = f.QueryField;
                            memberName = Regex.Replace(memberName, @"\w+\(+(.+?)\)+", "$1");
                            args = $"${checkPrefix(memberName)}";
                        }
                        var methodBson = methodCallObj.CustomReturn != null ? methodCallObj.CustomReturn as BsonDocument : new BsonDocument("$" + method, args);
                        if (groupInfo != null)
                        {
                            groupInfo?.Add(f.ResultName, methodBson);
                            projectDic.Add(f.ResultName, "$" + f.ResultName);
                        }
                        else//仅筛选
                        {
                            projectDic?.Add(f.ResultName, methodBson);
                        }
                    }
                }
                else
                {
                    var be = f.CRLExpression as CRLExpression;
                    if (be != null)
                    {
                        var exp = query.BinaryCRLExpression(be.Left, be.Right, be.ExpType, true);
                        projectDic.Add(f.ResultName, exp.Filter);
                        continue;
                    }
                    if (groupInfo != null)
                    {
                        projectDic.Add(f.ResultName, "$_id." + f.FieldName);
                    }
                    else
                    {
                        var prex = typeof(T) == f.ModelType ? "" : query.GetPrefix(f.ModelType, "");
                        if (f.FieldName.ToLower() == "id" && prex == "")
                        {
                            continue;
                        }
                        var fName = f.FieldName;
                        if (fName.ToLower() == "id")
                        {
                            fName = "_id";
                        }
                        projectDic.Add(f.ResultName, $"${prex}{fName}");
                    }
                }
            }
            //没有select id 则排除默认_id
            if (!selectField.Any(b => b.QueryField.ToLower() == "id"))
            {
                projectDic.Add("_id", 0);
            }
            if (!projectDic.Any())
            {
                throw new System.Exception("projectDic is empty");
            }
            pipeline.Add(new BsonDocument("$project", projectDic));
            return projectDic;
        }
        BsonDocument createGroup<T>(List<BsonDocument> pipeline, MongoDBLambdaQuery<T> query)
        {
            var groupFields = query.__GroupFields;
            if (groupFields == null)
            {
                return null;
            }
            var groupInfo = new BsonDocument();
            var groupField = new BsonDocument();
            foreach (var f in groupFields)
            {
                var prex = typeof(T) == f.ModelType ? "" : query.GetPrefix(f.ModelType, "");
                groupField.Add(f.FieldName, $"${prex}{f.FieldName}");
            }
            groupInfo.Add("_id", groupField);
            pipeline.Add(new BsonDocument("$group", groupInfo));
            return groupInfo;
        }
        BsonDocument createMatch<T>(List<BsonDocument> pipeline, MongoDBLambdaQuery<T> query)
        {
            //var bs = RenderToBsonDocument(query.__MongoDBFilter);
            var bs = query.__MongoDBFilter;
            if (!bs.Any())
            {
                return null;
            }
            pipeline.Add(new BsonDocument("$match", bs));
            return bs;
        }
        void createSort<T>(List<BsonDocument> pipeline, MongoDBLambdaQuery<T> query, BsonDocument projection)
        {
            var selectField = query.GetFieldMapping();
            if (query.__sortFields.Any())
            {

                var bs = new BsonDocument();
                query.__sortFields.ForEach(f =>
                {
                    var resultName = f.Item1.ResultName;
                    if (string.IsNullOrEmpty(resultName))
                    {
                        var f2 = selectField.Find(b => b.QueryField == f.Item1.QueryField);
                        if (f2 != null)//从selectField里查找
                        {
                            resultName = f2.ResultName;
                        }
                        if (string.IsNullOrEmpty(resultName))//按生成的默认值算
                        {
                            resultName = f.Item1.QueryField;
                        }
                    }
                    bs.Add(resultName, f.Item2 ? -1 : 1);
                });
                pipeline.Add(new BsonDocument("$sort", bs));
            }

            foreach (var f in query.__sortFields.Where(b => string.IsNullOrEmpty(b.Item1.ResultName)))
            {
                //扩展方法排序时，生成默认的projection
                var f2 = selectField.Find(b => b.QueryField == f.Item1.QueryField);
                if (f2 == null)
                {
                    projection?.Add(f.Item1.QueryField, "$" + f.Item1.QueryField);
                }
            }
        }
    }
}
